--- START OF FILE view_images.py ---

# Import the global object 'g' from Flask to access request-specific data.
from flask import g
# Import a specific widget for text fields from Flask-AppBuilder.
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
# Import the SQLAlchemy data interface from Flask-AppBuilder.
from flask_appbuilder.models.sqla.interface import SQLAInterface
# Import the lazy translation function from Flask-Babel.
from flask_babel import lazy_gettext as _
# Import a form field for string input from WTForms.
from wtforms import StringField
# Import validators from WTForms to validate form input.
from wtforms.validators import DataRequired, Length, Regexp

# Import application-specific components.
from myapp.app import app, appbuilder
from myapp.forms import MyBS3TextAreaFieldWidget
from myapp.models.model_job import Images, Repository
from myapp.third.k8s.py_k8s import K8s

# Import base classes and mixins from the local 'base' module.
from .base import (
    DeleteMixin,
    MyappFilter,
    MyappModelView,
)
# Import the custom base API class.
from .baseApi import MyappModelRestApi


# Get the application configuration.
conf = app.config


# This base class defines the common configuration for Repository model views (both UI and API).
class Repository_ModelView_Base:
    # Set the data model to Repository using the SQLAlchemy interface.
    datamodel = SQLAInterface(Repository)

    # label_title is used to generate UI titles like "List Repository", "Add Repository", etc.
    label_title = '仓库'
    # URL to redirect to after certain actions. It's fetched from the application config.
    check_redirect_list_url = conf.get('MODEL_URLS', {}).get('repository', '')
    # Define the base permissions for this view.
    base_permissions = ['can_add', 'can_edit', 'can_delete', 'can_list', 'can_show']
    # Set the default sorting order for the list view (by id, descending).
    base_order = ('id', 'desc')
    # Specify the columns that can be used for sorting.
    order_columns = ['id']
    # Specify the columns that are searchable.
    search_columns = ['name', 'server', 'hubsecret', 'user']
    # Specify the columns to display in the list view.
    list_columns = ['name', 'hubsecret', 'creator', 'modified', 'changed_on']
    # Define custom widths for specific columns in the list view UI.
    cols_width = {
        'name': {'type': 'ellip2', 'width': 250},
        'hubsecret': {'type': 'ellip2', 'width': 250},
    }
    # show_exclude_columns = ['password']
    # Specify the columns to display in the add form.
    add_columns = ['name', 'server', 'user', 'password', 'hubsecret']
    # Specify the columns to display in the edit form.
    edit_columns = add_columns

    # Define extra fields to customize the add form, overriding the defaults.
    add_form_extra_fields = {
        'server': StringField(
            # Get the translated label for the 'server' field.
            _(datamodel.obj.lab('server')),
            # Use a standard Bootstrap 3 text field widget.
            widget=BS3TextFieldWidget(),
            # Set a default value for the field.
            default='harbor.oa.com',
            # Provide a helpful description for the user.
            description='镜像仓库服务地址',
        ),
        'user': StringField(
            _(datamodel.obj.lab('user')),
            default='',
            widget=BS3TextFieldWidget(),
            description='镜像仓库的用户名',
        ),
        'password': StringField(
            _(datamodel.obj.lab('password')),
            default='',
            widget=BS3TextFieldWidget(),
            description='镜像仓库的链接密码',
        ),
    }

    # Use the same extra fields for the edit form as for the add form.
    edit_form_extra_fields = add_form_extra_fields

    # @pysnooper.snoop()
    # This method dynamically sets up extra fields for the add form.
    def set_column(self):
        # Customize the 'name' field with a default value based on the current user's username.
        self.add_form_extra_fields['name'] = StringField(
            _(self.datamodel.obj.lab('name')),
            default=g.user.username + '-',
            widget=BS3TextFieldWidget(),
            description='仓库名称',
        )

        # Customize the 'hubsecret' field with a default value and validators.
        self.add_form_extra_fields['hubsecret'] = StringField(
            _(self.datamodel.obj.lab('hubsecret')),
            default=g.user.username + '-hubsecret',
            widget=BS3TextFieldWidget(),
            description='在k8s中创建的hub secret',
            # Add validators to ensure the secret name is a valid Kubernetes resource name.
            validators=[Regexp(r'^[a-z][a-z0-9\-]*[a-z0-9]$'), Length(1, 54), DataRequired()],
        )

    # Register the 'set_column' method to be called before rendering the add form.
    pre_add_get = set_column

    # create hubsecret
    # @pysnooper.snoop()
    # This method applies the repository credentials as a secret in Kubernetes clusters.
    def apply_hubsecret(self, hubsecret):
        # Get all configured Kubernetes clusters.
        all_cluster = conf.get('CLUSTERS', {})
        # Collect all unique KubeConfig paths from the cluster configurations.
        all_kubeconfig = [
            all_cluster[cluster].get('KUBECONFIG', '') for cluster in all_cluster
        ] + ['']
        all_kubeconfig = list(set(all_kubeconfig))
        # Iterate over each KubeConfig to target different clusters.
        for kubeconfig in all_kubeconfig:
            # Initialize a Kubernetes client for the current cluster.
            k8s = K8s(kubeconfig)
            # Get the list of namespaces where the secret should be created.
            namespaces = conf.get('HUBSECRET_NAMESPACE')
            # Iterate over the target namespaces.
            for namespace in namespaces:
                # Call the Kubernetes client to create or update the image pull secret.
                k8s.apply_hubsecret(
                    namespace=namespace,
                    name=hubsecret.hubsecret,
                    user=hubsecret.user,
                    password=hubsecret.password,
                    server=hubsecret.server,
                )

    # This is a Flask-AppBuilder hook that runs after a new item is successfully added.
    def post_add(self, item):
        # After adding a new repository, apply its credentials as a Kubernetes secret.
        self.apply_hubsecret(item)

    # This is a Flask-AppBuilder hook that runs after an item is successfully updated.
    def post_update(self, item):
        # After updating a repository, re-apply its credentials to update the Kubernetes secret.
        self.apply_hubsecret(item)


# This class provides the REST API for the Repository model.
class Repository_ModelView_Api(Repository_ModelView_Base, MyappModelRestApi):
    # Set the data model to Repository.
    datamodel = SQLAInterface(Repository)

    # Define the base route for this API.
    route_base = '/repository_modelview/api'

    # This is a Flask-AppBuilder hook that runs before a new item is added via the API.
    def pre_add(self, item):
        # 检查镜像仓库是否能够登陆
        # client = get_client(timeout=5)
        # Strip leading/trailing whitespace from credentials before saving.
        item.server = item.server.strip()
        item.user = item.user.strip()
        item.password = item.password.strip()

        # The following block is commented out, but it was intended to validate repository credentials.
        # try:
        #     client.login(item.user, password=item.password, registry=item.server)
        # except Exception as e:
        #     logging.error(repr(e))
        #     raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, '镜像仓库域名或用户名/密码不正确')


# Register the Repository API with the Flask-AppBuilder instance.
appbuilder.add_api(Repository_ModelView_Api)


# 只能查看到自己归属的项目组的镜像
# A custom filter for the Images model to restrict visibility based on user roles.
class Images_Filter(MyappFilter):
    # @pysnooper.snoop(watch_explode=('result'))
    # The apply method is called by Flask-AppBuilder to modify the query.
    def apply(self, query, func):
        # Get the current user's roles in lowercase.
        user_roles = [role.name.lower() for role in list(self.get_user_roles())]
        # If the user is an admin, they can see all images.
        if 'admin' in user_roles:
            # Return the query ordered by ID descending.
            return query.order_by(self.model.id.desc())

        # For non-admin users, this currently just orders the query.
        # It seems intended to have more filtering logic here.
        result = query.order_by(self.model.id.desc())
        return result


# This base class defines the common configuration for Images model views.
class Images_ModelView_Base:
    label_title = '镜像'
    # Set the data model to Images.
    datamodel = SQLAInterface(Images)
    check_redirect_list_url = conf.get('MODEL_URLS', {}).get('images', '')

    # Define the columns to show in the list view.
    list_columns = ['images_url', 'creator', 'modified']
    # Define custom column widths for the UI.
    cols_width = {
        'images_url': {'type': 'ellip2', 'width': 500},
    }
    # Define the columns that are searchable.
    search_columns = ['created_by', 'project', 'repository', 'name', 'describe']
    # Set the default sort order.
    base_order = ('id', 'desc')
    # Define the columns that are sortable.
    order_columns = ['id']
    # Define the columns for the add form.
    add_columns = ['repository', 'name', 'describe', 'dockerfile', 'gitpath']
    # Define the columns for the edit form.
    edit_columns = add_columns

    # Define extra fields to customize the add form.
    add_form_extra_fields = {
        'dockerfile': StringField(
            _(datamodel.obj.lab('dockerfile')),
            description='镜像的构建Dockerfile全部内容',
            default='',
            # Use a custom text area widget with 10 rows.
            widget=MyBS3TextAreaFieldWidget(rows=10),
        ),
        'name': StringField(
            _(datamodel.obj.lab('name')),
            description='镜像名称全称，例如ubuntu:20.04',
            default='',
            widget=BS3TextFieldWidget(),
        ),
        'entrypoint': StringField(
            _(datamodel.obj.lab('entrypoint')),
            description='镜像的入口命令，直接写成单行字符串，例如python xx.py，无需添加[]',
            default='',
            widget=BS3TextFieldWidget(),
        ),
    }

    # Use the same extra fields for the edit form.
    edit_form_extra_fields = add_form_extra_fields
    # Apply the custom Images_Filter to all queries for this view.
    base_filters = [['id', Images_Filter, lambda: []]]


# This class provides the standard web UI for the Images model.
class Images_ModelView(Images_ModelView_Base, MyappModelView, DeleteMixin):
    # Set the data model to Images.
    datamodel = SQLAInterface(Images)


# Register the Images web view with Flask-AppBuilder, but do not add it to the menu.
appbuilder.add_view_no_menu(Images_ModelView)


# This class provides the REST API for the Images model.
class Images_ModelView_Api(Images_ModelView_Base, MyappModelRestApi):
    # Set the data model to Images.
    datamodel = SQLAInterface(Images)
    # Define the base route for this API.
    route_base = '/images_modelview/api'
    # Override the list columns for the API response.
    list_columns = ['images_url', 'modified', 'creator']


# Register the Images API with the Flask-AppBuilder instance.
appbuilder.add_api(Images_ModelView_Api)
